package shaka.groovy.swt

import org.eclipse.swt.SWT
import org.eclipse.swt.custom.CTabFolder
import org.eclipse.swt.custom.CTabItem
import org.eclipse.swt.events.FocusAdapter
import org.eclipse.swt.events.KeyAdapter
import org.eclipse.swt.events.KeyEvent
import org.eclipse.swt.events.MenuDetectListener
import org.eclipse.swt.events.MouseAdapter
import org.eclipse.swt.events.MouseMoveListener
import org.eclipse.swt.events.SelectionAdapter
import org.eclipse.swt.graphics.Font
import org.eclipse.swt.graphics.FontData
import org.eclipse.swt.graphics.Rectangle
import org.eclipse.swt.layout.FillLayout
import org.eclipse.swt.layout.FormAttachment
import org.eclipse.swt.layout.FormData
import org.eclipse.swt.layout.FormLayout
import org.eclipse.swt.layout.GridData
import org.eclipse.swt.layout.GridLayout
import org.eclipse.swt.layout.RowData
import org.eclipse.swt.layout.RowLayout
import org.eclipse.swt.widgets.Button
import org.eclipse.swt.widgets.Composite
import org.eclipse.swt.widgets.DateTime
import org.eclipse.swt.widgets.DirectoryDialog
import org.eclipse.swt.widgets.Display
import org.eclipse.swt.widgets.FileDialog
import org.eclipse.swt.widgets.Group
import org.eclipse.swt.widgets.Label
import org.eclipse.swt.widgets.Menu
import org.eclipse.swt.widgets.MenuItem
import org.eclipse.swt.widgets.MessageBox
import org.eclipse.swt.widgets.Monitor
import org.eclipse.swt.widgets.Shell
import org.eclipse.swt.widgets.Spinner
import org.eclipse.swt.widgets.Table
import org.eclipse.swt.widgets.TableColumn
import org.eclipse.swt.widgets.TableItem
import org.eclipse.swt.widgets.Text
import org.eclipse.swt.widgets.ToolBar
import org.eclipse.swt.widgets.ToolItem
import org.eclipse.swt.widgets.TrayItem

public class SwtBuilder extends BuilderSupport {
	private Map id = [:]
	private Display _display
	private Shell _shell
	private LinkedList _swtList
	private Map _funcMap = [
		// class mapping
		shell       : Shell,
		composite   : Composite,
		group       : Group,
		label       : Label,
		button      : Button,
		text        : Text,
		spinner     : Spinner,
		group       : Group,
		dateTime    : DateTime,
		// tab folder
		cTabFolder  : CTabFolder,
		cTabItem    : CTabItem,
		selection : { Map attr, Object value ->
			CTabFolder folder = _swtList.getLast()
			folder.setSelection(value)
		},
		menu        : Menu,
		menuItem    : MenuItem,
		toolBar     : ToolBar,
		toolItem    : ToolItem,
		fileDialog  : FileDialog,
		dirDialog   : DirectoryDialog,
		dialog      : MessageBox,
		table       : Table,
		tableColumn : { Map attr, Object value ->
			Table table = _swtList.getLast()
			table.setHeaderVisible(true)

			attr = appendAttrC(attr, table)

			if(value && value instanceof Collection){
				for(Object v : value){
					TableColumn column = objCreate(attr.clone(), TableColumn)
					column.setText(v)
				}
			}
		},
		tableColumnW : { Map attr, Collection value ->
			Table table = _swtList.getLast()
			for (int i = 0; i < value.size(); i++) {
				table.getColumn(i).setWidth(value[i])
			}
		},
		tableItem : { Map attr, Object value ->
			Table table = _swtList.getLast()
			table.setHeaderVisible(true)

			attr = appendAttrC(attr, table)

			if(value && value instanceof Collection){
				TableItem item = objCreate(attr.clone(), TableItem)
				Collection c = value
				for (int i = 0; i < c.size(); i++) {
					Object v = c[i]
					item.setText(i, v)
				}
			}
		},
		// alert
		alert : { Map attr, Object value ->
			MessageBox dialog = new MessageBox(_shell)
			dialog.text = attr.title ?: 'ALERT !!!'
			dialog.message = attr.message
			dialog.open()
		},
		// layout
		fillLayout : { Map attr, Object value ->
			return layoutCreate(attr, _swtList.getLast(), FillLayout)
		},
		rowLayout : { Map attr, Object value ->
			return layoutCreate(attr, _swtList.getLast(), RowLayout)
		},
		gridLayout : { Map attr, Object value ->
			return layoutCreate(attr, _swtList.getLast(), GridLayout)
		},
		formLayout : { Map attr, Object value ->
			return layoutCreate(attr, _swtList.getLast(), FormLayout)
		},
		// layout data
		rowData  : { Map attr, Object value ->
			layoutDataCreate(attr, _swtList.getLast(), RowData)
		},
		gridData : { Map attr, Object value ->
			layoutDataCreate(attr, _swtList.getLast(), GridData)
		},
		formData : { Map attr, Object value ->
			def swt = _swtList.getLast()

			FormData data = new FormData()
			if(attr?.top)
				data.top = FormAttachment.class.newInstance(attr.top as Object[])
			if(attr?.left)
				data.left = FormAttachment.class.newInstance(attr.left as Object[])
			if(attr?.bottom)
				data.bottom = FormAttachment.class.newInstance(attr.bottom as Object[])
			if(attr?.right)
				data.right = FormAttachment.class.newInstance(attr.right as Object[])

			swt.setLayoutData(data)
		},
		// method invoke
		font : { Map attr, Object value ->
			attr = appendAttrC(attr, _display)
			_swtList.getLast().setFont(objCreate(attr, Font))
		},
		fontSize : { Map attr, Object value ->
			def swt = _swtList.getLast()
			def fontDatas = swt.getFont().getFontData()
			for(FontData fontData in fontDatas)
				swtInvoke(attr, fontData)
			swt.setFont(new Font(_display, fontDatas))
		},
		pack : { Map attr, Object value ->
			def swt = _swtList.getLast()
			if(swt.class == Table){
				Table table = swt
				for (int i = 0; i < table.getColumnCount(); i++) {
					table.getColumn(i).pack()
				}
			}else{
				swt.pack()
			}
		},
		open : { Map attr, Object value ->
			def swt = _swtList.getLast()
			swt.open()
		},
		// event
		event : { Map attr, Object value ->
			def swt = _swtList.getLast()
			if(attr.containsKey('click')){
				swt.addSelectionListener([widgetSelected : attr.get('click')] as SelectionAdapter)
			}
			if(attr.containsKey('rclick')){
				swt.addMenuDetectListener([menuDetected : attr.get('rclick')] as MenuDetectListener)
			}
			if(attr.containsKey('dbclick') || attr.containsKey('mousedown') || attr.containsKey('mouseup')){
				Map adapter = [:]
				if(attr.containsKey('dbclick'))
					adapter.mouseDoubleClick = attr.get('dbclick')
				if(attr.containsKey('mousedown'))
					adapter.mouseDown = attr.get('mousedown')
				if(attr.containsKey('mouseup'))
					adapter.mouseUp = attr.get('mouseup')
				swt.addMouseListener(adapter as MouseAdapter)
			}
			if(attr.containsKey('mousemove')){
				swt.addMouseMoveListener([mouseMove : attr.get('mousemove')] as MouseMoveListener)
			}
			if(attr.containsKey('focus') || attr.containsKey('blur')){
				Map adapter = [:]
				if(attr.containsKey('focus'))
					adapter.focusGained = attr.get('focus')
				if(attr.containsKey('blur'))
					adapter.focusLost = attr.get('blur')
				swt.addFocusListener(adapter as FocusAdapter)
			}
			if(attr.containsKey('keydown') || attr.containsKey('keyup')){
				Map adapter = [:]
				if(attr.containsKey('keydown'))
					adapter.keyPressed = attr.get('keydown')
				if(attr.containsKey('keyup'))
					adapter.keyReleased = attr.get('keyup')
				swt.addKeyListener(adapter as KeyAdapter)
			}
		},
		ctrla : { Map attr, Object value ->
			def swt = _swtList.getLast()
			swt.addKeyListener([keyReleased:{ KeyEvent event ->
					if ( event.stateMask == SWT.CTRL && event.keyCode == 97 ) {
						((Text)event.widget).selectAll()
					}
				}] as KeyAdapter)
		},
		// setMenu
		setMenu : { Map attr, Object value ->
			def swt = _swtList.getLast()
			swt.setMenu(value)
		},
		// tray
		trayItem : { Map attr, Object value ->
			attr = appendAttrC(attr, _display.getSystemTray())
			TrayItem trayItem = objCreate(attr, TrayItem)
			swtInvoke(attr, trayItem)
			return trayItem
		},
		defaultItem : { Map attr, Object value ->
			_swtList.getLast().setDefaultItem(value)
		}
	]

	public SwtBuilder(){
		_display = new Display()
		_swtList = new LinkedList()
		_swtList.add(_display)
		id.put('display', _display)
	}

	public Object getAt(String key){
		return id.get(key)
	}

	@Override
	protected Object createNode(Object name) {
		return createNode(name,null,null)
	}

	@Override
	protected Object createNode(Object name, Object value) {
		return createNode(name,null,value)
	}

	@Override
	protected Object createNode(Object name, Map attr) {
		return createNode(name,attr,null)
	}

	private String setter(String field){
		char[] cs = field.toCharArray()
		cs[0] = Character.toUpperCase(cs[0])
		return 'set' + new String(cs)
	}

	@Override
	protected Object createNode(Object name, Map attr, Object value) {
		Object swt
		try{
			Object mValue = _funcMap[name]
			if(mValue == null)
				return null
			if(mValue instanceof Closure) {
				Closure func = mValue
				swt = func(attr, value)
				return null
			}
			Class clazz = mValue
			// 處理 parent
			def parent
			CTabItem cTabItem
			if(attr?.containsKey('parent')){
				parent = attr.remove('parent')
			}else{
				parent = _swtList.getLast()
				// 處理 tab (1)
				if(parent.class == CTabItem){
					cTabItem = parent
					parent = _swtList[-2]
				}
			}
			// 建構 swt
			attr = appendAttrC(attr, parent)
			swt = objCreate(attr, clazz)
			swtInvoke(attr, swt)
			// 處理 tab (2)
			cTabItem?.setControl(swt)
			return swt
		}finally{
			if(swt != null){
				_swtList.add(swt)
				if(_shell == null && swt instanceof Shell)
					_shell = swt
			}else{
				// 沒有產生 swt 時就放置一個空字串(對應 nodeCompleted 時移除使用)
				_swtList.add('')
			}
		}
	}

	/**
	 * 在 c 最前面加上 appendObj (設置 SWT 的 parent 用)
	 * @param attr
	 * @param appendObj
	 * @return
	 */
	private Map appendAttrC(Map attr, Object appendObj){
		if(attr == null)
			attr = [:]
		if(attr?.containsKey('c')){
			List list = attr.get('c')
			list.add(0, appendObj)
		}else{
			attr.put('c', [appendObj])
		}
		return attr
	}

	/**
	 * 建立 SWT Widget, 會處理 attr 中的 [id, parent. c]
	 * @param attr
	 * @param clazz
	 * @return
	 */
	private objCreate(Map attr, Class clazz){
		def _id
		if(attr?.containsKey('id')){ _id = attr.remove('id') }

		def parent
		if(attr?.containsKey('parent')){
			parent = attr.remove('parent')
		}
		if(parent)
			attr = appendAttrC(attr, parent)

		def obj
		if(attr?.containsKey('c'))
			obj = clazz.newInstance(attr.remove('c') as Object[])
		else
			obj = clazz.newInstance()

		if(_id){ this.id.put(_id, obj) }

		return obj
	}

	private layoutCreate(Map attr, swt, Class clazz){
		def layout = objCreate(attr, clazz)
		swt.setLayout(layout)
		swtInvoke(attr, swt)
		return layout
	}

	private layoutDataCreate(Map attr, swt, Class clazz){
		def rowData = objCreate(attr, clazz)
		swtInvoke(attr, rowData)
		swt.setLayoutData(rowData)
		return rowData
	}

	/**
	 * 呼叫 swt, 會處理 [prop]
	 * @param attr
	 * @param swt
	 * @return
	 */
	private swtInvoke(Map attr, swt) {
		if(attr?.containsKey('prop')){
			Map prop = attr.remove('prop')
			prop.each { k, v ->
				swt."$k" = v
			}
		}
		attr?.each { k, v ->
			if(k == 'self' && v instanceof Closure){
				Closure closure = v
				closure.delegate = swt
				closure(swt)
				return
			}
			String setter = setter(k)
			try{
				swt."$setter"(v)
			}catch(MissingMethodException e){
				swt."$k"(v)
			}catch(NoSuchMethodException e){
				swt."$k"(v)
			}
		}
	}

	@Override
	protected void setParent(Object parent, Object child) {
	}

	@Override
	protected void nodeCompleted(Object parent, Object node) {
		_swtList.removeLast()
	}

	public void swtExec(Closure c){
		Display.getDefault().asyncExec(c as Runnable)
	}

	public void setLocation(Shell shell, double xx = 0.5, double yy = 0.5){
		Monitor monitor = shell.getMonitor()
		Rectangle bounds = monitor.getBounds()
		Rectangle rect = shell.getBounds()
		int x = bounds.x + (bounds.width - rect.width) * xx
		int y = bounds.y + (bounds.height - rect.height) * yy
		shell.setLocation(x, y)
	}

	public void show(){
		_shell.open()
		while (!_shell.isDisposed()) {
			if (!_display.readAndDispatch()) {
				_display.sleep()
			}
		}
		_display.dispose()
	}
}
